<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>1.8. 箭头函数（Arrow Functions）</title>
</head>
<body>

</body>
<script>
    /*
    ECMAScript 6 最有意思的部分之一就是箭头函数。正如其名，箭头函数由 “箭头”（=>）这种新的语法来定义。
    但是箭头函数的表现在以下几个重要的方面不同于传统的 JavaScript 函数：

    1、没有 this，super，arguments 和 new.target 绑定 - this，super，arguments 和 new.target 的值由最近的不包含箭头函数的作用域决定。（super 会在第四章讲解）
    2、不能被 new 调用 - 箭头函数内部没有 [[Construct]] 方法，因此不能当作构造函数使用。使用 new 调用箭头函数会抛出错误
    3、没有 prototype - 既然你不能使用 new 调用箭头函数，那么 prototype 就没有存在的理由。箭头函数没有 prototype 属性。
    4、不能更改 this - this 的值在函数内部不能被修改。在函数的整个生命周期内 this 的值是永恒不变的。
    5、没有 arguments 对象 - 既然箭头函数没有 arguments 绑定，你必须依赖于命名或者剩余参数来访问该函数的参数。
    6、不允许重复的命名参数 - 不论是在严格模式还是非严格模式下，箭头函数都不允许重复的命名参数存在，相比传统的函数，它们只有在严格模式下才禁止该种行为。
    * */
    //============================ 1.8.1. 箭头函数语法（Arrow Function Syntax）=======================
    /*
    根据你的需求，箭头函数的语法可以有多种形体。所有变体都是以参数为开头，箭头紧随其后，函数主体为结尾。参数和主体根据用途可以有不同的形式，例如下面的箭头函数接收单个参数并返回它：

    var reflect = value => value;

    // 等同于：

    var reflect = function(value) {
        return value;
    };
    当箭头函数只有一个参数时，该参数可以直接使用而不需要额外的语法。之后则是箭头和需要计算的表达式，即使没有显式书写 return 语句也会返回该参数。
     */
    var reflect = value => value;

    // 等同于：

    var reflect = function(value) {
        return value;
    };

    var sum = (num1, num2) => num1 + num2;

    // effectively equivalent to:

    var sum = function(num1, num2) {
        return num1 + num2;
    };

    var getName = () => "Nicholas";

    // 等同于：

    var getName = function() {
        return "Nicholas";
    };


    //如果你想创建空函数，那么就必须使用花括号，像这样：

    var doNothing = () => {};

    // 等同于：

    var doNothing = function() {};

    // 花括号代表函数的主体，以上的示例中花括号的使用到目前为止一切正常。
    // 然而当不想使用传统的函数主体形式返回一个对象字面量的时候，必须将该对象放在括号中。例如：
    var getTempItem = id => ({ id: id, name: "Temp" });

    // 等同于：

    var getTempItem = function(id) {

        return {
            id: id,
            name: "Temp"
        };
    };

    //=====================================1.8.2. 创建即用函数表达式（Creating Immediately-Invoked Function Expressions）========================
    console.group('-----------------1.8.2. 创建即用函数表达式（Creating Immediately-Invoked Function Expressions）-----------');
    /*
        在 JavaScript 中一种流行的函数使用方式是创建即用函数表达式（immediately-invoked function expressions, IIFEs）。
        IIFEs 允许你创建匿名函数并立即调用它，没有创建任何引用。
        当你想开拓一个不受项目中其它代码干扰的独立作用域时，这种方法特别好用，例如：
     */
    console.group('--------------开拓一个不受项目中其它代码干扰的独立作用域----------------');
    let person = function(name) {

        return {
            getName: function() {
                return name;
            }
        };

    }("Nicholas");

    console.log(person.getName());      // "Nicholas"
    console.groupEnd();
    /*
    在这段代码中，IIFE 创建了一个包含 getName() 方法的新对象。该方法返回传入的参数值，并显著地使 name 成为该对象的私有成员。
     */
    console.group('--------------开拓一个不受项目中其它代码干扰的独立作用域，你可以用括号包裹箭头函数来达到同样的效果：----------------');
    //你可以用括号包裹箭头函数来达到同样的效果：
    let p = ((name) => {

        return {
            getName: function() {
                return name;
            }
        };

    })("Nicholas");

    console.log(p.getName());      // "Nicholas"
    console.groupEnd();

    //===================================1.8.3. 无 this 绑定（No this Binding）==========================================
    console.group('---------------------1.8.3. 无 this 绑定（No this Binding）,请点击一下空白页面---------------------------');
    /*
        JavaScript 编程中最常见的错误之一就是函数中 this 的绑定。
        由于函数内部的 this 可以在调用时被上下文替换，
        所以操作了意想不到的对象的几率很大。
        考虑如下的例子：
     */
    var PageHandler = {

        id: "123456",

        init: function() {
            //this.doSomething() 出现故障是因为 this 指代的是调用该函数的对象（在本例中是 document）而不是PageHandler。
            // document.addEventListener("click", function(event) {
            //     this.doSomething(event.type);     // error
            // }, false);

            // 你可以显式地在函数上调用 bind() 来绑定 this 以便解决这个问题，像这样：
            document.addEventListener("click", (function(event) {
                this.doSomething(event.type);     // 无错误发生
            }).bind(this), false);
        },

        doSomething: function(type) {
            console.log("Handling " + type  + " for " + this.id);
        }
    };
    PageHandler.init();
    /*
    现在代码如我们想象的那样运行，不过看起来有些奇怪。
    通过调用 bind(this)，
    你实际上创建了一个带有 this 绑定的新函数。
    为了避免额外函数的创建，更好的方式是使用箭头函数。
     */

    // 箭头函数没有 this 绑定，意味着 this 只能通过查找作用域链来确定。
    // 如果箭头函数被另一个不包含箭头函数的函数囊括，那么 this 的值和该函数中的 this 相等，
    // 否则 this 的值为 undefined。以下就是使用箭头函数重构的示例：
    var PageHandler2 = {

        id: "123456",

        init: function() {
            document.addEventListener("click",
                event => this.doSomething(event.type), false);
        },

        doSomething: function(type) {
            console.log("Handling " + type  + " for " + this.id);
        }
    };
    PageHandler2.init();
    /*
    本例中 this.doSomething() 是在处理事件的箭头回调函数中调用。
    this 的值和 init() 方法相同，所以以上代码的作用和 bind(this) 类似。
    虽然 doSomething() 方法并不返回任何值，它仍然是函数主体中唯一需要运行的语句，
    所以花括号的使用没有必要。

    箭头函数被定义为 “用完即扔” 的函数，所以不能被用来定义新类型；
    证据是箭头函数不存在一般函数中包含的 prototype 属性。
    使用 new 来调用箭头函数会发生错误，如下所示：

    var MyType = () => {},
        object = new MyType();  // 错误 - 你不能使用 new 调用箭头函数
    在这段代码中，调用 new MyType() 失败的原因是箭头函数内部不存在 [[Construct]] 方法。
    箭头函数不能被 new 调用的特性使得 JavaScript 引擎能对它们做更深一步的优化。

    同样，箭头函数中 this 的值由包含它的函数决定，所以你没有办法通过 call()，apply() 或 bind() 方法来改变 this 的值。
     */
    console.groupEnd();
    //==============================================1.8.4. 箭头函数与数组（Arrow Functions and Arrays）================================
    /*
    箭头函数写法的简洁特性也非常适合操作数组。例如你想自定义数组元素的排序方式，一般会使用下面的写法：

    var result = values.sort(function(a, b) {
        return a - b;
    });
    这些代码对如此简单的需求来讲显得冗余，相比如下使用箭头函数重构的版本更是如此：

    var result = values.sort((a, b) => a - b);
    使用回调函数的数组方法，例如 sort()，map() 和 reduce() 都能享受箭头函数语法带来的好处 —— 把复杂的代码简单化。
     */
    //==============================================1.8.5. 无 arguments 绑定（No arguments Binding）==================================
    /*
    虽然箭头函数没有自己的 arguments 对象，不过访问包含它们的函数中的 arguments 对象还是可行的。该对象不论箭头函数何时执行都能访问。例如：

    function createArrowFunctionReturningFirstArg() {
        return () => arguments[0];
    }

    var arrowFunction = createArrowFunctionReturningFirstArg(5);

    console.log(arrowFunction());       // 5
    在 createArrowFunctionReturningFirstArg() 内部，arguments[0] 元素被创建的箭头函数所引用。
    该元素为 传入 createArrowFunctionReturningFirstArg() 函数的首个参数。当箭头函数在随后执行时，返回的值为 5，
    这也正是传给示例中传给 createArrowFunctionReturningFirstArg() 的参数。
    虽然 createArrowFunctionReturningFirstArg() 被调用后箭头函数将不存在于该函数作用域内，
    但是作用域链中的 arguments 标识符依旧可以访问。
     */
    //=============================================1.8.6. 查找箭头函数（Identifying Arrow Functions）====================================
    console.group('---------------------------1.8.6. 查找箭头函数（Identifying Arrow Functions）-------------------------------');
    var comparator = (a, b) => a - b;

    console.log(typeof comparator);                 // "function"
    console.log(comparator instanceof Function);    // true
    //console.log() 输出的内容显示 typeof 和 instanceof 操作箭头函数的表现和其它函数完全一致。
    //同样，你也可以对箭头函数使用 call()，apply()，bind()，即使它们的 this 不受影响。如下所示：
    var sum = (num1, num2) => num1 + num2;

    console.log(sum.call(null, 1, 2));      // 3
    console.log(sum.apply(null, [1, 2]));   // 3

    var boundSum = sum.bind(null, 1, 2);

    console.log(boundSum());                // 3
    /*
    sum() 函数被 call 和 apply() 调用并传递参数，类似于你使用其它函数的方式。bind() 方法创建了 boundSum() 函数，意味着 sum() 方法的参数已被绑定为 1 和 2，再次调用不需要传入参数。

    你使用的任何匿名函数都能被箭头函数所替代，例如回调函数。下一节要讲述的是 ECMAScript 6 另一项主要的开发内容，不过它是由内部实现的，同时没有新的语法出现。

     */

    console.groupEnd();
</script>
</html>
